/*
 *
 *  *    Copyright 2020-2021 Luter.me
 *  *
 *  *    Licensed under the Apache License, Version 2.0 (the "License");
 *  *    you may not use this file except in compliance with the License.
 *  *    You may obtain a copy of the License at
 *  *
 *  *      http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  *    Unless required by applicable law or agreed to in writing, software
 *  *    distributed under the License is distributed on an "AS IS" BASIS,
 *  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  *    See the License for the specific language governing permissions and
 *  *    limitations under the License.
 *
 */

package com.luter.heimdall.core.authorization.handler;

import com.luter.heimdall.core.annotation.*;
import com.luter.heimdall.core.authorization.authority.GrantedAuthority;
import com.luter.heimdall.core.exception.HeimdallUnauthorizedException;
import com.luter.heimdall.core.manager.AuthenticationManager;
import com.luter.heimdall.core.manager.AuthorizationManager;
import com.luter.heimdall.core.utils.StrUtils;
import org.slf4j.Logger;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

import static org.slf4j.LoggerFactory.getLogger;

/**
 * 方法注解权限判断
 * <p>
 * 处理所有认证和授权的注解
 *
 * @author luter
 */
public abstract class HeimdallMethodAuthorizationHandler {
    /**
     * The constant log.
     */
    private static final transient Logger log = getLogger(HeimdallMethodAuthorizationHandler.class);

    /**
     * Instantiates a new Auth way method authorization handler.
     */
    public HeimdallMethodAuthorizationHandler() {
    }

    /**
     * 方法注解权限判断
     *
     * @param authorizationManager 授权管理器
     * @param method               方法
     */
    public static void handleMethodAnnotation(AuthorizationManager authorizationManager, Method method) {
        if (null == authorizationManager || null == method) {
            return;
        }
        if (method.isAnnotationPresent(RequiresUser.class)
                || method.getDeclaringClass().isAnnotationPresent(RequiresUser.class)) {
            handleRequiresUser(authorizationManager, method);
        }
        if (method.isAnnotationPresent(RequiresRole.class)
                || method.getDeclaringClass().isAnnotationPresent(RequiresRole.class)) {
            handleRequiresRole(authorizationManager, method);
        }
        if (method.isAnnotationPresent(RequiresRoles.class)
                || method.getDeclaringClass().isAnnotationPresent(RequiresRoles.class)) {
            handleRequiresRoles(authorizationManager, method);
        }
        if (method.isAnnotationPresent(RequiresPermission.class)
                || method.getDeclaringClass().isAnnotationPresent(RequiresPermission.class)) {
            handleRequiresPermission(authorizationManager, method);
        }
        if (method.isAnnotationPresent(RequiresPermissions.class)
                || method.getDeclaringClass().isAnnotationPresent(RequiresPermissions.class)) {
            handleRequiresPermissions(authorizationManager, method);
        }
    }

    /**
     * 需要登录才能访问
     *
     * @param authorizationManager the authorization manager
     * @param method               the method
     */
    public static void handleRequiresUser(AuthorizationManager authorizationManager, Method method) {
        handleRequiresUser(authorizationManager.getAuthenticationManager(), method);
    }

    /**
     * 需要登录才能访问
     *
     * @param authenticationManager the authentication manager
     * @param method                the method
     */
    public static void handleRequiresUser(AuthenticationManager authenticationManager, Method method) {
        log.debug("[handleRequiresUser]::method = [{}]", method);
        final RequiresUser annotation = findAnnotation(method, RequiresUser.class);
        if (null != annotation) {
            log.info("[handleRequiresUser]::Access Denied. method = [{}]", method);
            authenticationManager.isAuthenticated(true);
        }
    }

    /**
     * 需要一个角色才能访问
     *
     * @param authorizationManager the authorization manager
     * @param method               the method
     */
    public static void handleRequiresRole(AuthorizationManager authorizationManager, Method method) {
        log.debug("[handleRequiresRole]:: method = [{}]", method);
        final RequiresRole annotation = findAnnotation(method, RequiresRole.class);
        if (null != annotation) {
            String roleName = annotation.value();
            if (!hasPermission(authorizationManager, roleName)) {
                log.debug("[handleRequiresRole]:: Access Denied. method = [{}]", method);
                throw new HeimdallUnauthorizedException("You are not authorized. Role:[" + roleName + "]. Access denied.");
            }
        }
    }

    /**
     * 需要一个或者多个角色才能访问
     *
     * @param authorizationManager the authorization manager
     * @param method               the method
     */
    public static void handleRequiresRoles(AuthorizationManager authorizationManager, Method method) {
        log.debug("[handleRequiresRoles]::method = [{}]", method);
        final RequiresRoles annotation = findAnnotation(method, RequiresRoles.class);
        if (null != annotation) {
            String[] roles = annotation.value();
            Logical logical = annotation.logical();
            if (roles.length > 0) {
                if (Logical.ALL.equals(logical)) {
                    if (!hasAllPermissions(authorizationManager, roles)) {
                        log.error("[handleRequiresRoles]::authorized failed.roles = [{}],logical = [{}],method = [{}]",
                                Arrays.toString(roles), logical, method.getName());
                        throw new HeimdallUnauthorizedException("You are not authorized . All Roles:[" + Arrays.toString(roles) + "]. Access denied.");
                    }
                } else {
                    if (!hasAnyPermissions(authorizationManager, roles)) {
                        log.error("[handleRequiresRoles]::authorized failed.roles = [{}],logical = [{}],method = [{}]",
                                Arrays.toString(roles), logical, method.getName());
                        throw new HeimdallUnauthorizedException("You are not authorized . Any Roles:[" + Arrays.toString(roles) + "]. Access denied.");
                    }
                }
            }
        }
    }

    /**
     * 需要权限标识才能访问
     *
     * @param authorizationManager the authorization manager
     * @param method               the method
     */
    public static void handleRequiresPermission(AuthorizationManager authorizationManager, Method method) {
        log.debug("[handleRequiresPermission]::method = [{}]", method);
        final RequiresPermission annotation = findAnnotation(method, RequiresPermission.class);
        if (null != annotation) {
            String perm = annotation.value();
            if (StrUtils.isNotBlank(perm)) {
                log.info("Aspect Authorizer= Rule :[RequiresPermission],required permission: {}", perm);
                if (!hasPermission(authorizationManager, perm)) {
                    log.error("[handleRequiresPermission]::authorized failed .permission = [{}], method = [{}]", perm, method);
                    throw new HeimdallUnauthorizedException("You are not authorized   :[ " + perm + "]. Access denied.");
                }
            }
        }
    }

    /**
     * 需要一个或者多个具备权限标识才能访问
     *
     * @param authorizationManager the authorization manager
     * @param method               the method
     */
    public static void handleRequiresPermissions(AuthorizationManager authorizationManager, Method method) {
        log.debug("[handleRequiresPermissions]:: method = [{}]", method);
        final RequiresPermissions annotation = findAnnotation(method, RequiresPermissions.class);
        if (null != annotation) {
            String[] permissions = annotation.value();
            Logical logical = annotation.logical();
            log.info("Aspect Authorizer= Rule :[RequiresPermissions],required Permissions: [{}],logical:[{}]", permissions, logical);
            if (permissions.length > 0) {
                if (Logical.ALL.equals(logical)) {
                    if (!hasAllPermissions(authorizationManager, permissions)) {
                        log.error("[handleRequiresRoles]::authorized failed.roles = [{}],logical = [{}],method = [{}]",
                                Arrays.toString(permissions), logical, method.getName());
                        throw new HeimdallUnauthorizedException("You do not have all of the authorities   :[" + Arrays.toString(permissions) + "]. Access denied.");
                    }
                } else {
                    if (!hasAnyPermissions(authorizationManager, permissions)) {
                        log.error("[handleRequiresRoles]::authorized failed.roles = [{}],logical = [{}],method = [{}]",
                                Arrays.toString(permissions), logical, method.getName());
                        throw new HeimdallUnauthorizedException("You do not have any of the authorities :[" + Arrays.toString(permissions) + "]. Access denied.");
                    }
                }
            }
        }
    }

    /**
     * Has permission boolean.
     *
     * @param authorizationManager the authorization manager
     * @param perm                 the perm
     * @return the boolean
     */
    public static boolean hasPermission(AuthorizationManager authorizationManager, String perm) {
        return hasPermission(getCurrentUserAuthorities(authorizationManager), perm);
    }

    /**
     * Has permission boolean.
     *
     * @param userAuthorities the user authorities
     * @param perm            the perm
     * @return the boolean
     */
    public static boolean hasPermission(Collection<? extends GrantedAuthority> userAuthorities, String perm) {
        if (null == userAuthorities || userAuthorities.isEmpty()) {
            return false;
        }
        final List<String> collect = userAuthorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
        return hasIdentifier(collect, perm);
    }

    /**
     * Has all permissions boolean.
     *
     * @param authorizationManager the authorization manager
     * @param perms                the perms
     * @return the boolean
     */
    public static boolean hasAllPermissions(AuthorizationManager authorizationManager, String... perms) {
        return hasAllPermissions(getCurrentUserAuthorities(authorizationManager), perms);
    }

    /**
     * Has all permissions boolean.
     *
     * @param userAuthorities the user authorities
     * @param perms           the perms
     * @return the boolean
     */
    public static boolean hasAllPermissions(Collection<? extends GrantedAuthority> userAuthorities, String... perms) {
        if (null == userAuthorities || userAuthorities.isEmpty()) {
            return false;
        }
        final List<String> collect = userAuthorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
        return hasAllIdentifiers(collect, perms);
    }

    /**
     * Has any permissions boolean.
     *
     * @param authorizationManager the authorization manager
     * @param perms                the perms
     * @return the boolean
     */
    public static boolean hasAnyPermissions(AuthorizationManager authorizationManager, String... perms) {
        return hasAnyPermissions(getCurrentUserAuthorities(authorizationManager), perms);
    }

    /**
     * Has any permissions boolean.
     *
     * @param userAuthorities the user authorities
     * @param perms           the perms
     * @return the boolean
     */
    public static boolean hasAnyPermissions(Collection<? extends GrantedAuthority> userAuthorities, String... perms) {
        if (null == userAuthorities || userAuthorities.isEmpty()) {
            return false;
        }
        final List<String> collect = userAuthorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
        return hasAnyIdentifiers(collect, perms);
    }

    /**
     * 获取当前用户的权限
     * <p>
     * 未登录抛出异常
     *
     * @param authorizationManager the authorization manager
     * @return the current user authorities
     */
    public static Collection<? extends GrantedAuthority> getCurrentUserAuthorities(AuthorizationManager authorizationManager) {
        return authorizationManager.getAuthorizationStore()
                .getUserAuthorities(authorizationManager
                        .getAuthenticationManager().getCurrentUser(true));
    }

    /**
     * 获取注解
     * <p>
     * 先从方法获取，方法么有，从方法的类上面获取
     *
     * @param <A>            注解
     * @param method         方法
     * @param annotationType 注解类型
     * @return the a
     */
    private static <A extends Annotation> A findAnnotation(Method method, Class<A> annotationType) {
        final Class<?> targetClass = method.getDeclaringClass();
        final A annotation = targetClass.getAnnotation(annotationType);
        if (null == annotation) {
            return method.getAnnotation(annotationType);
        }
        return annotation;
    }

    /**
     * Has identifier boolean.
     *
     * @param needIdentifiers the need identifiers
     * @param hasIdentifier   the has identifier
     * @return the boolean
     */
    private static boolean hasIdentifier(List<String> needIdentifiers, String hasIdentifier) {
        return null != needIdentifiers && !needIdentifiers.isEmpty() && needIdentifiers.contains(hasIdentifier);
    }

    /**
     * Has all identifiers boolean.
     *
     * @param needIdentifiers the need identifiers
     * @param hasIdentifiers  the has identifiers
     * @return the boolean
     */
    private static boolean hasAllIdentifiers(List<String> needIdentifiers, String[] hasIdentifiers) {
        if (null != hasIdentifiers && hasIdentifiers.length > 0 && null != needIdentifiers && !needIdentifiers.isEmpty()) {
            return needIdentifiers.containsAll(Arrays.asList(hasIdentifiers));
        }
        return false;
    }

    /**
     * Has any identifiers boolean.
     *
     * @param needIdentifiers the need identifiers
     * @param hasIdentifiers  the has identifiers
     * @return the boolean
     */
    private static boolean hasAnyIdentifiers(List<String> needIdentifiers, String[] hasIdentifiers) {
        if (null != hasIdentifiers && hasIdentifiers.length > 0 && null != needIdentifiers && !needIdentifiers.isEmpty()) {
            for (String hasIdentifier : hasIdentifiers) {
                if (needIdentifiers.contains(hasIdentifier)) {
                    log.debug("[hasAnyIdentifiers]::needIdentifiers = [{}], hasIdentifiers = [{}],Matched = [{}]", needIdentifiers, hasIdentifiers, hasIdentifier);
                    return true;
                }
            }
        }
        return false;
    }
}
